/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

package org.unidata.mdm.meta.context;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.unidata.mdm.core.context.AbstractModelChangeContext;
import org.unidata.mdm.core.service.segments.ModelUpsertStartExecutor;
import org.unidata.mdm.draft.context.DraftDataContext;
import org.unidata.mdm.meta.configuration.TypeIds;
import org.unidata.mdm.meta.type.model.DataModel;
import org.unidata.mdm.meta.type.model.entities.EntitiesGroup;
import org.unidata.mdm.meta.type.model.entities.Entity;
import org.unidata.mdm.meta.type.model.entities.LookupEntity;
import org.unidata.mdm.meta.type.model.entities.NestedEntity;
import org.unidata.mdm.meta.type.model.entities.Relation;
import org.unidata.mdm.meta.util.ModelUtils;
import org.unidata.mdm.system.context.DraftAwareContext;
import org.unidata.mdm.system.context.StorageSpecificContext;
import org.unidata.mdm.system.type.pipeline.PipelineInput;

/**
 * @author Mikhail Mikhailov
 *         Container for meta model updates.
 */
public class UpsertDataModelContext
        extends AbstractModelChangeContext
        implements DraftAwareContext, DraftDataContext, PipelineInput, StorageSpecificContext, Serializable {
    /**
     * Generated SVUID.
     */
    private static final long serialVersionUID = 8533210708984117919L;
    /**
     * Top level entity updates
     */
    private final List<Entity> entitiesUpdate;
    /**
     * Lookup entity updates.
     */
    private final List<LookupEntity> lookupEntitiesUpdate;
    /**
     * Nested entity updates.
     */
    private final List<NestedEntity> nestedEntitiesUpdate;
    /**
     * Relations updates.
     */
    private final List<Relation> relationsUpdate;
    /**
     * Entities group updates.
     */
    private final EntitiesGroup entitiesGroupsUpdate;
    /**
     * Entity IDs to delete..
     */
    private final List<String> entitiesDelete;
    /**
     * Lookup entity IDs to delete..
     */
    private final List<String> lookupEntitiesDelete;
    /**
     * Nested entities Ids to delete
     */
    private final List<String> nestedEntitiesDelete;
    /**
     * Relation IDs.
     */
    private final List<String> relationsDelete;
    /**
     * A possibly set draft id.
     */
    private final Long draftId;
    /**
     * A possibly set parent draft id.
     */
    private final Long parentDraftId;
    /**
     * Constructor.
     */
    private UpsertDataModelContext(UpsertDataModelContextBuilder b) {
        super(b);
        this.entitiesUpdate = Objects.isNull(b.entitiesUpdate) ? Collections.emptyList() : b.entitiesUpdate;
        this.lookupEntitiesUpdate = Objects.isNull(b.lookupEntitiesUpdate) ? Collections.emptyList() : b.lookupEntitiesUpdate;
        this.nestedEntitiesUpdate = Objects.isNull(b.nestedEntitiesUpdate) ? Collections.emptyList() : b.nestedEntitiesUpdate;
        this.relationsUpdate = Objects.isNull(b.relationsUpdate) ? Collections.emptyList() : b.relationsUpdate;
        this.entitiesDelete = Objects.isNull(b.entitiesDelete) ? Collections.emptyList() : b.entitiesDelete;
        this.lookupEntitiesDelete = Objects.isNull(b.lookupEntitiesDelete) ? Collections.emptyList() : b.lookupEntitiesDelete;
        this.nestedEntitiesDelete = Objects.isNull(b.nestedEntitiesDelete) ? Collections.emptyList() : b.nestedEntitiesDelete;
        this.relationsDelete = Objects.isNull(b.relationsDelete) ? Collections.emptyList() : b.relationsDelete;
        this.entitiesGroupsUpdate = b.entitiesGroupsUpdate;
        this.draftId = b.draftId;
        this.parentDraftId = b.parentDraftId;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getTypeId() {
        return TypeIds.DATA_MODEL;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getInstanceId() {
        // Data models are singletons per storage
        return ModelUtils.DEFAULT_MODEL_INSTANCE_ID;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getStartTypeId() {
        return ModelUpsertStartExecutor.SEGMENT_ID;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public Long getDraftId() {
        return draftId;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public Long getParentDraftId() {
        return parentDraftId;
    }
    /**
     * @return the entityUpdate
     */
    public List<Entity> getEntitiesUpdate() {
        return entitiesUpdate;
    }
    /**
     * @return the lookupEntityUpdate
     */
    public List<LookupEntity> getLookupEntitiesUpdate() {
        return lookupEntitiesUpdate;
    }
    /**
     * @return the nestedEntityUpdate
     */
    public List<NestedEntity> getNestedEntitiesUpdate() {
        return nestedEntitiesUpdate;
    }
    /**
     * @return the relationsUpdate
     */
    public List<Relation> getRelationsUpdate() {
        return relationsUpdate;
    }
    /**
     * @return entitiesGroupsUpdate
     */
    public EntitiesGroup getEntitiesGroupsUpdate() {
        return entitiesGroupsUpdate;
    }
    /**
     * @return the entitiesDelete
     */
    public List<String> getEntitiesDelete() {
        return entitiesDelete;
    }
    /**
     * @return the lookupEntitiesDelete
     */
    public List<String> getLookupEntitiesDelete() {
        return lookupEntitiesDelete;
    }
    /**
     * @return the nestedEntitiesDelete
     */
    public List<String> getNestedEntitiesDelete() {
        return nestedEntitiesDelete;
    }
    /**
     * @return the relationsDelete
     */
    public List<String> getRelationsDelete() {
        return relationsDelete;
    }
    /**
     * Has entity update.
     *
     * @return true if so false otherwise
     */
    public boolean hasEntitiesUpdate() {
        return entitiesUpdate != null && !entitiesUpdate.isEmpty();
    }
    /**
     * Has lookup entity update.
     *
     * @return true if so false otherwise
     */
    public boolean hasLookupEntitiesUpdate() {
        return lookupEntitiesUpdate != null && !lookupEntitiesUpdate.isEmpty();
    }
    /**
     * Has nested entity update.
     *
     * @return true if so false otherwise
     */
    public boolean hasNestedEntitiesUpdate() {
        return nestedEntitiesUpdate != null && !nestedEntitiesUpdate.isEmpty();
    }
    /**
     * Has relations update.
     *
     * @return true if so false otherwise
     */
    public boolean hasRelationsUpdate() {
        return relationsUpdate != null && !relationsUpdate.isEmpty();
    }
    /**
     * Has group update.
     *
     * @return true, if has, false otherwise
     */
    public boolean hasEntitiesGroupUpdate() {
        return entitiesGroupsUpdate != null;
    }
    /**
     * Has entity delete.
     *
     * @return true if so false otherwise
     */
    public boolean hasEntitiesDelete() {
        return CollectionUtils.isNotEmpty(entitiesDelete);
    }
    /**
     * Has lookup entity delete.
     *
     * @return true if so false otherwise
     */
    public boolean hasLookupEntitiesDelete() {
        return CollectionUtils.isNotEmpty(lookupEntitiesDelete);
    }
    /**
     * Has nested entity update.
     *
     * @return true if so false otherwise
     */
    public boolean hasNestedEntitiesDelete() {
        return CollectionUtils.isNotEmpty(nestedEntitiesDelete);
    }
    /**
     * Has relations update.
     *
     * @return true if so false otherwise
     */
    public boolean hasRelationsDelete() {
        return CollectionUtils.isNotEmpty(relationsDelete);
    }

    public static UpsertDataModelContextBuilder builder() {
        return new UpsertDataModelContextBuilder();
    }

    public static UpsertDataModelContextBuilder builder(DataModel model) {
        return new UpsertDataModelContextBuilder()
                .entitiesUpdate(model.getEntities())
                .lookupEntitiesUpdate(model.getLookupEntities())
                .nestedEntitiesUpdate(model.getNestedEntities())
                .relationsUpdate(model.getRelations());
    }
    /**
     * @author Mikhail Mikhailov
     *         Request context builder.
     */
    public static class UpsertDataModelContextBuilder extends AbstractModelChangeContextBuilder<UpsertDataModelContextBuilder> {
        /**
         * Top level entity updates
         */
        private List<Entity> entitiesUpdate;
        /**
         * Lookup entity updates.
         */
        private List<LookupEntity> lookupEntitiesUpdate;
        /**
         * Nested entity updates.
         */
        private List<NestedEntity> nestedEntitiesUpdate;
        /**
         * Relations updates.
         */
        private List<Relation> relationsUpdate;
        /**
         * Entity IDs to delete..
         */
        private List<String> entitiesDelete;
        /**
         * Lookup entity IDs to delete..
         */
        private List<String> lookupEntitiesDelete;
        /**
         * Nested entities Ids to delete
         */
        private List<String> nestedEntitiesDelete;
        /**
         * Relation IDs.
         */
        private List<String> relationsDelete;
        /**
         * Entities group updates
         */
        private EntitiesGroup entitiesGroupsUpdate = null;
        /**
         * The draft id.
         */
        private Long draftId;
        /**
         * The parent draft id.
         */
        private Long parentDraftId;
        /**
         * Constructor.
         */
        private UpsertDataModelContextBuilder() {
            super();
        }
        /**
         * Sets draft id
         * @param draftId the draft id
         * @return self
         */
        public UpsertDataModelContextBuilder draftId(Long draftId) {
            this.draftId = draftId;
            return self();
        }
        /**
         * Sets parent draft id
         * @param parentDraftId the parent draft id
         * @return self
         */
        public UpsertDataModelContextBuilder parentDraftId(Long parentDraftId) {
            this.parentDraftId = parentDraftId;
            return self();
        }
        /**
         * Sets entity update.
         *
         * @param entityUpdate the update
         * @return self
         */
        public UpsertDataModelContextBuilder entitiesUpdate(Entity... entityUpdate) {
            if (ArrayUtils.isNotEmpty(entityUpdate)) {
                return entitiesUpdate(Arrays.asList(entityUpdate));
            }
            return this;
        }
        /**
         * Sets entity update.
         *
         * @param entitiesUpdate the update
         * @return self
         */
        public UpsertDataModelContextBuilder entitiesUpdate(List<Entity> entitiesUpdate) {
            if (CollectionUtils.isNotEmpty(entitiesUpdate)) {
                if (this.entitiesUpdate == null) {
                    this.entitiesUpdate = new ArrayList<>();
                }
                this.entitiesUpdate.addAll(entitiesUpdate);
            }
            return this;
        }
        /**
         * Sets lookup entity update.
         *
         * @param lookupEntityUpdate the update
         * @return self
         */
        public UpsertDataModelContextBuilder lookupEntitiesUpdate(LookupEntity... lookupEntityUpdate) {
            if (ArrayUtils.isNotEmpty(lookupEntityUpdate)) {
                return lookupEntitiesUpdate(Arrays.asList(lookupEntityUpdate));
            }
            return this;
        }
        /**
         * Sets lookup entity update.
         *
         * @param lookupEntitiesUpdate the update
         * @return self
         */
        public UpsertDataModelContextBuilder lookupEntitiesUpdate(List<LookupEntity> lookupEntitiesUpdate) {
            if (CollectionUtils.isNotEmpty(lookupEntitiesUpdate)) {
                if (this.lookupEntitiesUpdate == null) {
                    this.lookupEntitiesUpdate = new ArrayList<>();
                }
                this.lookupEntitiesUpdate.addAll(lookupEntitiesUpdate);
            }
            return this;
        }
        /**
         * Sets nested entity update.
         *
         * @param nestedEntitiesUpdate the update
         * @return self
         */
        public UpsertDataModelContextBuilder nestedEntitiesUpdate(NestedEntity... nestedEntityUpdate) {
            if (ArrayUtils.isNotEmpty(nestedEntityUpdate)) {
                return nestedEntitiesUpdate(Arrays.asList(nestedEntityUpdate));
            }
            return this;
        }
        /**
         * Sets nested entity update.
         *
         * @param nestedEntitiesUpdate the update
         * @return self
         */
        public UpsertDataModelContextBuilder nestedEntitiesUpdate(List<NestedEntity> nestedEntitiesUpdate) {
            if (CollectionUtils.isNotEmpty(nestedEntitiesUpdate)) {
                if (this.nestedEntitiesUpdate == null) {
                    this.nestedEntitiesUpdate = new ArrayList<>();
                }
                this.nestedEntitiesUpdate.addAll(nestedEntitiesUpdate);
            }
            return this;
        }
        /**
         * Sets relations update.
         *
         * @param relationsUpdate the update
         * @return self
         */
        public UpsertDataModelContextBuilder relationsUpdate(Relation... relationUpdate) {
            if (ArrayUtils.isNotEmpty(relationUpdate)) {
                return relationsUpdate(Arrays.asList(relationUpdate));
            }
            return this;
        }
        /**
         * Sets relations update.
         *
         * @param relationsUpdate the update
         * @return self
         */
        public UpsertDataModelContextBuilder relationsUpdate(List<Relation> relationsUpdate) {
            if (CollectionUtils.isNotEmpty(relationsUpdate)) {
                if (this.relationsUpdate == null) {
                    this.relationsUpdate = new ArrayList<>();
                }
                this.relationsUpdate.addAll(relationsUpdate);
            }
            return this;
        }
        /**
         * Sets entity delete.
         *
         * @param entityDelete the delete
         * @return self
         */
        public UpsertDataModelContextBuilder entitiesDelete(String... entityDelete) {
            if (ArrayUtils.isNotEmpty(entityDelete)) {
                return entitiesDelete(Arrays.asList(entityDelete));
            }
            return this;
        }
        /**
         * Sets entity delete.
         *
         * @param entitiesDelete the delete
         * @return self
         */
        public UpsertDataModelContextBuilder entitiesDelete(List<String> entitiesDelete) {
            if (CollectionUtils.isNotEmpty(entitiesDelete)) {
                if (this.entitiesDelete == null) {
                    this.entitiesDelete = new ArrayList<>();
                }
                this.entitiesDelete.addAll(entitiesDelete);
            }
            return this;
        }
        /**
         * Sets lookup entity delete.
         *
         * @param lookupEntityDelete the delete
         * @return self
         */
        public UpsertDataModelContextBuilder lookupEntitiesDelete(String... lookupEntityDelete) {
            if (ArrayUtils.isNotEmpty(lookupEntityDelete)) {
                return lookupEntitiesDelete(Arrays.asList(lookupEntityDelete));
            }
            return this;
        }
        /**
         * Sets lookup entity delete.
         *
         * @param lookupEntitiesDelete the delete
         * @return self
         */
        public UpsertDataModelContextBuilder lookupEntitiesDelete(List<String> lookupEntitiesDelete) {
            if (CollectionUtils.isNotEmpty(lookupEntitiesDelete)) {
                if (this.lookupEntitiesDelete == null) {
                    this.lookupEntitiesDelete = new ArrayList<>();
                }
                this.lookupEntitiesDelete.addAll(lookupEntitiesDelete);
            }
            return this;
        }
        /**
         * Sets nested entity delete.
         *
         * @param nestedEntityDelete the delete
         * @return self
         */
        public UpsertDataModelContextBuilder nestedEntitiesDelete(String... nestedEntityDelete) {
            if (ArrayUtils.isNotEmpty(nestedEntityDelete)) {
                return nestedEntitiesDelete(Arrays.asList(nestedEntityDelete));
            }
            return this;
        }
        /**
         * Sets nested entity delete.
         *
         * @param nestedEntitiesDelete the delete
         * @return self
         */
        public UpsertDataModelContextBuilder nestedEntitiesDelete(List<String> nestedEntitiesDelete) {
            if (CollectionUtils.isNotEmpty(nestedEntitiesDelete)) {
                if (this.nestedEntitiesDelete == null) {
                    this.nestedEntitiesDelete = new ArrayList<>();
                }
                this.nestedEntitiesDelete.addAll(nestedEntitiesDelete);
            }
            return this;
        }
        /**
         * Sets relations delete.
         *
         * @param relationDelete the delete
         * @return self
         */
        public UpsertDataModelContextBuilder relationsDelete(String... relationDelete) {
            if (ArrayUtils.isNotEmpty(relationDelete)) {
                return relationsDelete(Arrays.asList(relationDelete));
            }
            return this;
        }
        /**
         * Sets relations delete.
         *
         * @param relationsDelete the delete
         * @return self
         */
        public UpsertDataModelContextBuilder relationsDelete(List<String> relationsDelete) {
            if (CollectionUtils.isNotEmpty(relationsDelete)) {
                if (this.relationsDelete == null) {
                    this.relationsDelete = new ArrayList<>();
                }
                this.relationsDelete.addAll(relationsDelete);
            }
            return this;
        }
        /**
         * @param entitiesGroupsUpdate
         * @return
         */
        public UpsertDataModelContextBuilder entitiesGroupsUpdate(EntitiesGroup entitiesGroupsUpdate) {
            this.entitiesGroupsUpdate = entitiesGroupsUpdate;
            return this;
        }
        /**
         * Builder method.
         *
         * @return new context
         */
        @Override
        public UpsertDataModelContext build() {
            return new UpsertDataModelContext(this);
        }
    }
}
